﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Reflection;
    using System.Security.Cryptography;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    using Phuse.Net.Nntp;

    /// <summary>Reports progress to the console.</summary>
    internal sealed class ConsoleProgressObserver : IProgress<ISendReceiveProgress>, ISendReceiveProgressVisitor
    {
        private readonly object padLock = new object();
        private readonly Dictionary<object, int> eventCounts = new Dictionary<object, int>();
        private int start;
        private Exception unexpectedException;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="IProgress{T}.Report"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by external code.")]
        public void Report(ISendReceiveProgress value)
        {
            value.Accept(this);
        }

        /// <summary>See <see cref="IVisitor{T,U}.OnVisit"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by external code.")]
        public void OnVisit(ISendReceiveProgress visitable)
        {
            throw new NotImplementedException("No implementation found for " + visitable.GetType() + ".");
        }

        /// <summary>See <see cref="IVisitor{T,U}.OnVisit"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by external code.")]
        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Will not be localized.")]
        public void OnVisit(ConnectProgress visitable)
        {
            if (visitable.Percent == 0)
            {
                this.start = Environment.TickCount;
            }

            this.AddProgress(visitable);
            Console.WriteLine(
                "Connecting to {0}: {1}% (@{2}ms)", visitable.HostName, visitable.Percent, this.ElapsedTime);
        }

        /// <summary>See <see cref="IVisitor{T,U}.OnVisit"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by external code.")]
        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Will not be localized.")]
        public void OnVisit(SendProgress visitable)
        {
            this.AddProgress(visitable);
            Console.WriteLine("Sending articles: {0}% (@{1}ms) ...", visitable.Percent, this.ElapsedTime);
        }

        /// <summary>See <see cref="IVisitor{T,U}.OnVisit"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by external code.")]
        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Will not be localized.")]
        public void OnVisit(HeaderReceiveProgress visitable)
        {
            this.AddProgress(visitable);
            Console.WriteLine(
                "Receiving headers from {0}: {1}% (@{2}ms) ...", visitable.DisplayName, visitable.Percent, this.ElapsedTime);
        }

        /// <summary>See <see cref="IVisitor{T,U}.OnVisit"/>.</summary>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", Justification = "Method is not supposed to be called by external code.")]
        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Will not be localized.")]
        public void OnVisit(ContentReceiveProgress visitable)
        {
            this.AddProgress(visitable);
            Console.WriteLine(
                "Receiving contents from {0}: {1}% (@{2}ms) ...", visitable.DisplayName, visitable.Percent, this.ElapsedTime);
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal ConsoleProgressObserver()
        {
        }

        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Will not be localized.")]
        internal void OnFinished()
        {
            this.AddCall(this.OnFinished);
            Console.WriteLine("Send/Receive finished. (@{0}ms)", this.ElapsedTime);
        }

        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Will not be localized.")]
        internal void OnStopped()
        {
            this.AddCall(this.OnStopped);
            Console.WriteLine("Send/Receive stopped. (@{0}ms)", this.ElapsedTime);
        }

        internal bool OnException(Exception exception)
        {
            bool handled =
                TryHandle<PostingNotPermittedException>(exception) ||
                TryHandle<PostingFailedException>(exception) ||
                TryHandle<UnknownCommandException>(exception) ||
                TryHandle<ServiceUnavailableException>(exception) ||
                TryHandle<ServerFaultException>(exception) ||
                TryHandle<NoSuchGroupException>(exception) ||
                TryHandle<MissingAuthenticationException>(exception) ||
                TryHandle<AuthenticationFailedException>(exception) ||
                TryHandle<CryptographicException>(exception) ||
                TryHandle<MissingPrivacyException>(exception) ||
                TryHandle<UnexpectedResponseException>(exception) ||
                TryHandle<HostUnreachableException>(exception) ||
                TryHandle<ConnectionLostException>(exception) ||
                TryHandle<WriteFailedException>(exception);

            if (!handled)
            {
                this.AddException(exception);
                this.SetException(exception);
            }

            return true;
        }

        internal void AssertCallCount(Exception exception, int expectedCount)
        {
            this.AssertEventCount(exception.GetType(), expectedCount);
        }

        internal void AssertEventCount(object ev, int expectedCount)
        {
            int count;

            lock (this.padLock)
            {
                this.eventCounts.TryGetValue(ev, out count);
            }

            Assert.AreEqual(expectedCount, count);
        }

        internal void AssertTotalEventCount(int expectedCount)
        {
            int count = 0;

            lock (this.padLock)
            {
                foreach (var pair in this.eventCounts)
                {
                    count += pair.Value;
                }
            }

            Assert.AreEqual(expectedCount, count);
        }

        internal void AssertNoExceptions()
        {
            lock (this.padLock)
            {
                foreach (var pair in this.eventCounts)
                {
                    var type = pair.Key as Type;

                    if ((type != null) && type.IsSubclassOf(typeof(Exception)))
                    {
                        Assert.AreEqual(0, pair.Value, "Exception: " + type);
                    }
                }
            }
        }

        internal void RethrowException()
        {
            lock (this.padLock)
            {
                if (this.unexpectedException != null)
                {
                    throw new TargetInvocationException(this.unexpectedException);
                }
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private int ElapsedTime
        {
            get { return Environment.TickCount - this.start; }
        }

        private bool TryHandle<TException>(Exception exception) where TException : Exception
        {
            bool result = exception is TException;

            if (result)
            {
                this.AddException(exception);
            }

            return result;
        }

        private void SetException(Exception ex)
        {
            lock (this.padLock)
            {
                this.unexpectedException = ex;
            }
        }

        private void AddCall(Action call)
        {
            this.AddEvent((object)call);
        }

        private void AddProgress(ISendReceiveProgress visitable)
        {
            this.AddEvent(visitable.GetType());
        }

        private void AddException(Exception ex)
        {
            this.AddEvent(ex.GetType());
        }

        private void AddEvent(object ev)
        {
            lock (this.padLock)
            {
                int count;
                this.eventCounts.TryGetValue(ev, out count);
                this.eventCounts[ev] = count + 1;
            }
        }
    }
}
